diff --git a/lib/types/crypto-types.js b/lib/types/crypto-types.js index d0184519d..ef86f5943 100644 --- a/lib/types/crypto-types.js +++ b/lib/types/crypto-types.js @@ -1,75 +1,75 @@ // @flow import t, { type TInterface } from 'tcomb'; import { tShape } from '../utils/validation-utils.js'; export type OLMIdentityKeys = { +ed25519: string, +curve25519: string, }; export type OLMPrekey = { +curve25519: { +id: string, +key: string, }, }; export type OLMOneTimeKeys = { +curve25519: { +[string]: string }, }; export type PickledOLMAccount = { +picklingKey: string, +pickledAccount: string, }; export type CryptoStore = { - +primaryAccount: ?PickledOLMAccount, - +primaryIdentityKeys: ?OLMIdentityKeys, - +notificationAccount: ?PickledOLMAccount, - +notificationIdentityKeys: ?OLMIdentityKeys, + +primaryAccount: PickledOLMAccount, + +primaryIdentityKeys: OLMIdentityKeys, + +notificationAccount: PickledOLMAccount, + +notificationIdentityKeys: OLMIdentityKeys, }; export type IdentityKeysBlob = { +primaryIdentityPublicKeys: OLMIdentityKeys, +notificationIdentityPublicKeys: OLMIdentityKeys, }; export type SignedIdentityKeysBlob = { +payload: string, +signature: string, }; export const signedIdentityKeysBlobValidator: TInterface = tShape({ payload: t.String, signature: t.String, }); // This type should not be changed without making equivalent changes to // `Message` in Identity service's `reserved_users` module export type ReservedUsernameMessage = | { +statement: 'Add the following usernames to reserved list', +payload: $ReadOnlyArray, +issuedAt: string, } | { +statement: 'Remove the following username from reserved list', +payload: string, +issuedAt: string, } | { +statement: 'This user is the owner of the following username and user ID', +payload: { +username: string, +userID: string, }, +issuedAt: string, }; export const olmEncryptedMessageTypes = Object.freeze({ PREKEY: 0, TEXT: 1, }); diff --git a/lib/types/redux-types.js b/lib/types/redux-types.js index 3947c5888..736468fc5 100644 --- a/lib/types/redux-types.js +++ b/lib/types/redux-types.js @@ -1,1266 +1,1266 @@ // @flow import type { LogOutResult, LogInStartingPayload, LogInResult, RegisterResult, DefaultNotificationPayload, ClaimUsernameResponse, } from './account-types.js'; import type { ActivityUpdateSuccessPayload, QueueActivityUpdatesPayload, SetThreadUnreadStatusPayload, } from './activity-types.js'; import type { UpdateUserAvatarRequest, UpdateUserAvatarResponse, } from './avatar-types.js'; import type { CryptoStore } from './crypto-types.js'; import type { GetVersionActionPayload, LastCommunicatedPlatformDetails, } from './device-types.js'; import type { ClientDBDraftInfo, DraftStore } from './draft-types.js'; import type { EnabledApps, SupportedApps } from './enabled-apps.js'; import type { RawEntryInfo, EntryStore, SaveEntryPayload, CreateEntryPayload, DeleteEntryResult, RestoreEntryPayload, FetchEntryInfosResult, CalendarQueryUpdateResult, CalendarQueryUpdateStartingPayload, CalendarQuery, } from './entry-types.js'; import type { CalendarFilter, CalendarThreadFilter, SetCalendarDeletedFilterPayload, } from './filter-types.js'; import type { IntegrityStore } from './integrity-types.js'; import type { KeyserverStore, AddKeyserverPayload, RemoveKeyserverPayload, } from './keyserver-types.js'; import type { LifecycleState } from './lifecycle-state-types.js'; import type { FetchInviteLinksResponse, InviteLink, InviteLinksStore, InviteLinkVerificationResponse, DisableInviteLinkPayload, } from './link-types.js'; import type { LoadingStatus, LoadingInfo } from './loading-types.js'; import type { UpdateMultimediaMessageMediaPayload } from './media-types.js'; import type { MessageReportCreationResult } from './message-report-types.js'; import type { MessageStore, RawMultimediaMessageInfo, FetchMessageInfosPayload, SendMessagePayload, EditMessagePayload, SaveMessagesPayload, NewMessagesPayload, MessageStorePrunePayload, LocallyComposedMessageInfo, ClientDBMessageInfo, SimpleMessagesPayload, ClientDBThreadMessageInfo, FetchPinnedMessagesResult, SearchMessagesResponse, } from './message-types.js'; import type { RawReactionMessageInfo } from './messages/reaction.js'; import type { RawTextMessageInfo } from './messages/text.js'; import type { BaseNavInfo } from './nav-types.js'; import { type ForcePolicyAcknowledgmentPayload, type PolicyAcknowledgmentPayload, type UserPolicies, } from './policy-types.js'; import type { RelationshipErrors } from './relationship-types.js'; import type { EnabledReports, ClearDeliveredReportsPayload, QueueReportsPayload, ReportStore, ClientReportCreationRequest, } from './report-types.js'; import type { ProcessServerRequestAction, GetOlmSessionInitializationDataResponse, } from './request-types.js'; import type { UserSearchResult, ExactUserSearchResult, } from './search-types.js'; import type { SetSessionPayload } from './session-types.js'; import type { StateSyncFullActionPayload, StateSyncIncrementalActionPayload, UpdateConnectionStatusPayload, SetLateResponsePayload, UpdateDisconnectedBarPayload, } from './socket-types.js'; import type { SubscriptionUpdateResult } from './subscription-types.js'; import type { GlobalThemeInfo } from './theme-types.js'; import type { ThreadActivityStore } from './thread-activity-types.js'; import type { ThreadStore, ChangeThreadSettingsPayload, LeaveThreadPayload, NewThreadResult, ThreadJoinPayload, ToggleMessagePinResult, RoleModificationPayload, RoleDeletionPayload, } from './thread-types.js'; import type { ClientUpdatesResultWithUserInfos } from './update-types.js'; import type { CurrentUserInfo, UserInfos, UserStore } from './user-types.js'; import type { SetDeviceTokenActionPayload } from '../actions/device-actions.js'; import type { Shape } from '../types/core.js'; import type { NotifPermissionAlertInfo } from '../utils/push-alerts.js'; export type BaseAppState = { +navInfo: NavInfo, +currentUserInfo: ?CurrentUserInfo, +draftStore: DraftStore, +entryStore: EntryStore, +threadStore: ThreadStore, +userStore: UserStore, +messageStore: MessageStore, +loadingStatuses: { [key: string]: { [idx: number]: LoadingStatus } }, +calendarFilters: $ReadOnlyArray, +notifPermissionAlertInfo: NotifPermissionAlertInfo, +actualizedCalendarQuery: CalendarQuery, +watchedThreadIDs: $ReadOnlyArray, +lifecycleState: LifecycleState, +enabledApps: EnabledApps, +reportStore: ReportStore, +nextLocalID: number, +dataLoaded: boolean, +userPolicies: UserPolicies, +commServicesAccessToken: ?string, +inviteLinksStore: InviteLinksStore, +keyserverStore: KeyserverStore, +threadActivityStore: ThreadActivityStore, +integrityStore: IntegrityStore, +globalThemeInfo: GlobalThemeInfo, ... }; export type NativeAppState = BaseAppState<>; export type WebAppState = BaseAppState<> & { - +cryptoStore: CryptoStore, + +cryptoStore: ?CryptoStore, +pushApiPublicKey: ?string, ... }; export type AppState = NativeAppState | WebAppState; export type BaseAction = | { +type: '@@redux/INIT', +payload?: void, } | { +type: 'FETCH_ENTRIES_STARTED', +payload?: void, +loadingInfo: LoadingInfo, } | { +type: 'FETCH_ENTRIES_FAILED', +error: true, +payload: Error, +loadingInfo: LoadingInfo, } | { +type: 'FETCH_ENTRIES_SUCCESS', +payload: FetchEntryInfosResult, +loadingInfo: LoadingInfo, } | { +type: 'LOG_OUT_STARTED', +payload?: void, +loadingInfo: LoadingInfo, } | { +type: 'LOG_OUT_FAILED', +error: true, +payload: Error, +loadingInfo: LoadingInfo, } | { +type: 'LOG_OUT_SUCCESS', +payload: LogOutResult, +loadingInfo: LoadingInfo, } | { +type: 'CLAIM_USERNAME_STARTED', +payload?: void, +loadingInfo: LoadingInfo, } | { +type: 'CLAIM_USERNAME_FAILED', +error: true, +payload: Error, +loadingInfo: LoadingInfo, } | { +type: 'CLAIM_USERNAME_SUCCESS', +payload: ClaimUsernameResponse, +loadingInfo: LoadingInfo, } | { +type: 'DELETE_ACCOUNT_STARTED', +payload?: void, +loadingInfo: LoadingInfo, } | { +type: 'DELETE_ACCOUNT_FAILED', +error: true, +payload: Error, +loadingInfo: LoadingInfo, } | { +type: 'DELETE_ACCOUNT_SUCCESS', +payload: LogOutResult, +loadingInfo: LoadingInfo, } | { +type: 'CREATE_LOCAL_ENTRY', +payload: RawEntryInfo, } | { +type: 'CREATE_ENTRY_STARTED', +payload?: void, +loadingInfo: LoadingInfo, } | { +type: 'CREATE_ENTRY_FAILED', +error: true, +payload: Error, +loadingInfo: LoadingInfo, } | { +type: 'CREATE_ENTRY_SUCCESS', +payload: CreateEntryPayload, +loadingInfo: LoadingInfo, } | { +type: 'SAVE_ENTRY_STARTED', +payload?: void, +loadingInfo: LoadingInfo, } | { +type: 'SAVE_ENTRY_FAILED', +error: true, +payload: Error, +loadingInfo: LoadingInfo, } | { +type: 'SAVE_ENTRY_SUCCESS', +payload: SaveEntryPayload, +loadingInfo: LoadingInfo, } | { +type: 'CONCURRENT_MODIFICATION_RESET', +payload: { +id: string, +dbText: string, }, } | { +type: 'DELETE_ENTRY_STARTED', +loadingInfo: LoadingInfo, +payload: { +localID: ?string, +serverID: ?string, }, } | { +type: 'DELETE_ENTRY_FAILED', +error: true, +payload: Error, +loadingInfo: LoadingInfo, } | { +type: 'DELETE_ENTRY_SUCCESS', +payload: ?DeleteEntryResult, +loadingInfo: LoadingInfo, } | { +type: 'LOG_IN_STARTED', +loadingInfo: LoadingInfo, +payload: LogInStartingPayload, } | { +type: 'LOG_IN_FAILED', +error: true, +payload: Error, +loadingInfo: LoadingInfo, } | { +type: 'LOG_IN_SUCCESS', +payload: LogInResult, +loadingInfo: LoadingInfo, } | { +type: 'REGISTER_STARTED', +loadingInfo: LoadingInfo, +payload: LogInStartingPayload, } | { +type: 'REGISTER_FAILED', +error: true, +payload: Error, +loadingInfo: LoadingInfo, } | { +type: 'REGISTER_SUCCESS', +payload: RegisterResult, +loadingInfo: LoadingInfo, } | { +type: 'CHANGE_USER_PASSWORD_STARTED', +payload?: void, +loadingInfo: LoadingInfo, } | { +type: 'CHANGE_USER_PASSWORD_FAILED', +error: true, +payload: Error, +loadingInfo: LoadingInfo, } | { +type: 'CHANGE_USER_PASSWORD_SUCCESS', +payload?: void, +loadingInfo: LoadingInfo, } | { +type: 'CHANGE_THREAD_SETTINGS_STARTED', +payload?: void, +loadingInfo: LoadingInfo, } | { +type: 'CHANGE_THREAD_SETTINGS_FAILED', +error: true, +payload: Error, +loadingInfo: LoadingInfo, } | { +type: 'CHANGE_THREAD_SETTINGS_SUCCESS', +payload: ChangeThreadSettingsPayload, +loadingInfo: LoadingInfo, } | { +type: 'DELETE_THREAD_STARTED', +payload?: void, +loadingInfo: LoadingInfo, } | { +type: 'DELETE_THREAD_FAILED', +error: true, +payload: Error, +loadingInfo: LoadingInfo, } | { +type: 'DELETE_THREAD_SUCCESS', +payload: LeaveThreadPayload, +loadingInfo: LoadingInfo, } | { +type: 'NEW_THREAD_STARTED', +payload?: void, +loadingInfo: LoadingInfo, } | { +type: 'NEW_THREAD_FAILED', +error: true, +payload: Error, +loadingInfo: LoadingInfo, } | { +type: 'NEW_THREAD_SUCCESS', +payload: NewThreadResult, +loadingInfo: LoadingInfo, } | { +type: 'REMOVE_USERS_FROM_THREAD_STARTED', +payload?: void, +loadingInfo: LoadingInfo, } | { +type: 'REMOVE_USERS_FROM_THREAD_FAILED', +error: true, +payload: Error, +loadingInfo: LoadingInfo, } | { +type: 'REMOVE_USERS_FROM_THREAD_SUCCESS', +payload: ChangeThreadSettingsPayload, +loadingInfo: LoadingInfo, } | { +type: 'CHANGE_THREAD_MEMBER_ROLES_STARTED', +payload?: void, +loadingInfo: LoadingInfo, } | { +type: 'CHANGE_THREAD_MEMBER_ROLES_FAILED', +error: true, +payload: Error, +loadingInfo: LoadingInfo, } | { +type: 'CHANGE_THREAD_MEMBER_ROLES_SUCCESS', +payload: ChangeThreadSettingsPayload, +loadingInfo: LoadingInfo, } | { +type: 'FETCH_REVISIONS_FOR_ENTRY_STARTED', +payload?: void, +loadingInfo: LoadingInfo, } | { +type: 'FETCH_REVISIONS_FOR_ENTRY_FAILED', +error: true, +payload: Error, +loadingInfo: LoadingInfo, } | { +type: 'FETCH_REVISIONS_FOR_ENTRY_SUCCESS', +payload: { +entryID: string, +text: string, +deleted: boolean, }, +loadingInfo: LoadingInfo, } | { +type: 'RESTORE_ENTRY_STARTED', +payload?: void, +loadingInfo: LoadingInfo, } | { +type: 'RESTORE_ENTRY_FAILED', +error: true, +payload: Error, +loadingInfo: LoadingInfo, } | { +type: 'RESTORE_ENTRY_SUCCESS', +payload: RestoreEntryPayload, +loadingInfo: LoadingInfo, } | { +type: 'JOIN_THREAD_STARTED', +payload?: void, +loadingInfo: LoadingInfo, } | { +type: 'JOIN_THREAD_FAILED', +error: true, +payload: Error, +loadingInfo: LoadingInfo, } | { +type: 'JOIN_THREAD_SUCCESS', +payload: ThreadJoinPayload, +loadingInfo: LoadingInfo, } | { +type: 'LEAVE_THREAD_STARTED', +payload?: void, +loadingInfo: LoadingInfo, } | { +type: 'LEAVE_THREAD_FAILED', +error: true, +payload: Error, +loadingInfo: LoadingInfo, } | { +type: 'LEAVE_THREAD_SUCCESS', +payload: LeaveThreadPayload, +loadingInfo: LoadingInfo, } | { +type: 'SET_NEW_SESSION', +payload: SetSessionPayload, } | { +type: 'persist/REHYDRATE', +payload: ?BaseAppState<>, } | { +type: 'FETCH_MESSAGES_BEFORE_CURSOR_STARTED', +payload?: void, +loadingInfo: LoadingInfo, } | { +type: 'FETCH_MESSAGES_BEFORE_CURSOR_FAILED', +error: true, +payload: Error, +loadingInfo: LoadingInfo, } | { +type: 'FETCH_MESSAGES_BEFORE_CURSOR_SUCCESS', +payload: FetchMessageInfosPayload, +loadingInfo: LoadingInfo, } | { +type: 'FETCH_MOST_RECENT_MESSAGES_STARTED', +payload?: void, +loadingInfo: LoadingInfo, } | { +type: 'FETCH_MOST_RECENT_MESSAGES_FAILED', +error: true, +payload: Error, +loadingInfo: LoadingInfo, } | { +type: 'FETCH_MOST_RECENT_MESSAGES_SUCCESS', +payload: FetchMessageInfosPayload, +loadingInfo: LoadingInfo, } | { +type: 'FETCH_SINGLE_MOST_RECENT_MESSAGES_FROM_THREADS_STARTED', +payload?: void, +loadingInfo: LoadingInfo, } | { +type: 'FETCH_SINGLE_MOST_RECENT_MESSAGES_FROM_THREADS_FAILED', +error: true, +payload: Error, +loadingInfo: LoadingInfo, } | { +type: 'FETCH_SINGLE_MOST_RECENT_MESSAGES_FROM_THREADS_SUCCESS', +payload: SimpleMessagesPayload, +loadingInfo: LoadingInfo, } | { +type: 'SEND_TEXT_MESSAGE_STARTED', +loadingInfo?: LoadingInfo, +payload: RawTextMessageInfo, } | { +type: 'SEND_TEXT_MESSAGE_FAILED', +error: true, +payload: Error & { +localID: string, +threadID: string, }, +loadingInfo?: LoadingInfo, } | { +type: 'SEND_TEXT_MESSAGE_SUCCESS', +payload: SendMessagePayload, +loadingInfo: LoadingInfo, } | { +type: 'SEND_MULTIMEDIA_MESSAGE_STARTED', +loadingInfo?: LoadingInfo, +payload: RawMultimediaMessageInfo, } | { +type: 'SEND_MULTIMEDIA_MESSAGE_FAILED', +error: true, +payload: Error & { +localID: string, +threadID: string, }, +loadingInfo?: LoadingInfo, } | { +type: 'SEND_MULTIMEDIA_MESSAGE_SUCCESS', +payload: SendMessagePayload, +loadingInfo: LoadingInfo, } | { +type: 'SEND_REACTION_MESSAGE_STARTED', +loadingInfo?: LoadingInfo, +payload: RawReactionMessageInfo, } | { +type: 'SEND_REACTION_MESSAGE_FAILED', +error: true, +payload: Error & { +localID: string, +threadID: string, +targetMessageID: string, +reaction: string, +action: string, }, +loadingInfo: LoadingInfo, } | { +type: 'SEND_REACTION_MESSAGE_SUCCESS', +payload: SendMessagePayload, +loadingInfo: LoadingInfo, } | { +type: 'SEARCH_USERS_STARTED', +payload?: void, +loadingInfo: LoadingInfo, } | { +type: 'SEARCH_USERS_FAILED', +error: true, +payload: Error, +loadingInfo: LoadingInfo, } | { +type: 'SEARCH_USERS_SUCCESS', +payload: UserSearchResult, +loadingInfo: LoadingInfo, } | { +type: 'EXACT_SEARCH_USER_STARTED', +payload?: void, +loadingInfo: LoadingInfo, } | { +type: 'EXACT_SEARCH_USER_FAILED', +error: true, +payload: Error, +loadingInfo: LoadingInfo, } | { +type: 'EXACT_SEARCH_USER_SUCCESS', +payload: ExactUserSearchResult, +loadingInfo: LoadingInfo, } | { +type: 'UPDATE_DRAFT', +payload: { +key: string, +text: string, }, } | { +type: 'MOVE_DRAFT', +payload: { +oldKey: string, +newKey: string, }, } | { +type: 'SET_CLIENT_DB_STORE', +payload: { +currentUserID: ?string, +drafts: $ReadOnlyArray, +messages: ?$ReadOnlyArray, +threadStore: ?ThreadStore, +messageStoreThreads: ?$ReadOnlyArray, +reports: ?$ReadOnlyArray, +users: ?UserInfos, }, } | { +type: 'UPDATE_ACTIVITY_STARTED', +payload?: void, +loadingInfo: LoadingInfo, } | { +type: 'UPDATE_ACTIVITY_FAILED', +error: true, +payload: Error, +loadingInfo: LoadingInfo, } | { +type: 'UPDATE_ACTIVITY_SUCCESS', +payload: ActivityUpdateSuccessPayload, +loadingInfo: LoadingInfo, } | { +type: 'SET_DEVICE_TOKEN_STARTED', +payload?: void, +loadingInfo: LoadingInfo, } | { +type: 'SET_DEVICE_TOKEN_FAILED', +error: true, +payload: Error, +loadingInfo: LoadingInfo, } | { +type: 'SET_DEVICE_TOKEN_SUCCESS', +payload: SetDeviceTokenActionPayload, +loadingInfo: LoadingInfo, } | { +type: 'SEND_REPORT_STARTED', +payload?: void, +loadingInfo: LoadingInfo, } | { +type: 'SEND_REPORT_FAILED', +error: true, +payload: Error, +loadingInfo: LoadingInfo, } | { +type: 'SEND_REPORT_SUCCESS', +payload?: ClearDeliveredReportsPayload, +loadingInfo: LoadingInfo, } | { +type: 'SEND_REPORTS_STARTED', +payload?: void, +loadingInfo: LoadingInfo, } | { +type: 'SEND_REPORTS_FAILED', +error: true, +payload: Error, +loadingInfo: LoadingInfo, } | { +type: 'SEND_REPORTS_SUCCESS', +payload?: ClearDeliveredReportsPayload, +loadingInfo: LoadingInfo, } | { +type: 'QUEUE_REPORTS', +payload: QueueReportsPayload, } | { +type: 'SET_URL_PREFIX', +payload: string, } | { +type: 'SAVE_MESSAGES', +payload: SaveMessagesPayload, } | { +type: 'UPDATE_CALENDAR_THREAD_FILTER', +payload: CalendarThreadFilter, } | { +type: 'CLEAR_CALENDAR_THREAD_FILTER', +payload?: void, } | { +type: 'SET_CALENDAR_DELETED_FILTER', +payload: SetCalendarDeletedFilterPayload, } | { +type: 'UPDATE_SUBSCRIPTION_STARTED', +payload?: void, +loadingInfo: LoadingInfo, } | { +type: 'UPDATE_SUBSCRIPTION_FAILED', +error: true, +payload: Error, +loadingInfo: LoadingInfo, } | { +type: 'UPDATE_SUBSCRIPTION_SUCCESS', +payload: SubscriptionUpdateResult, +loadingInfo: LoadingInfo, } | { +type: 'UPDATE_CALENDAR_QUERY_STARTED', +loadingInfo: LoadingInfo, +payload?: CalendarQueryUpdateStartingPayload, } | { +type: 'UPDATE_CALENDAR_QUERY_FAILED', +error: true, +payload: Error, +loadingInfo: LoadingInfo, } | { +type: 'UPDATE_CALENDAR_QUERY_SUCCESS', +payload: CalendarQueryUpdateResult, +loadingInfo: LoadingInfo, } | { +type: 'FULL_STATE_SYNC', +payload: StateSyncFullActionPayload, } | { +type: 'INCREMENTAL_STATE_SYNC', +payload: StateSyncIncrementalActionPayload, } | ProcessServerRequestAction | { +type: 'UPDATE_CONNECTION_STATUS', +payload: UpdateConnectionStatusPayload, } | { +type: 'QUEUE_ACTIVITY_UPDATES', +payload: QueueActivityUpdatesPayload, } | { +type: 'UNSUPERVISED_BACKGROUND', +payload: { +keyserverID: string }, } | { +type: 'UPDATE_LIFECYCLE_STATE', +payload: LifecycleState, } | { +type: 'ENABLE_APP', +payload: SupportedApps, } | { +type: 'DISABLE_APP', +payload: SupportedApps, } | { +type: 'UPDATE_REPORTS_ENABLED', +payload: Shape, } | { +type: 'PROCESS_UPDATES', +payload: ClientUpdatesResultWithUserInfos, } | { +type: 'PROCESS_MESSAGES', +payload: NewMessagesPayload, } | { +type: 'MESSAGE_STORE_PRUNE', +payload: MessageStorePrunePayload, } | { +type: 'SET_LATE_RESPONSE', +payload: SetLateResponsePayload, } | { +type: 'UPDATE_DISCONNECTED_BAR', +payload: UpdateDisconnectedBarPayload, } | { +type: 'REQUEST_ACCESS_STARTED', +payload?: void, +loadingInfo: LoadingInfo, } | { +type: 'REQUEST_ACCESS_FAILED', +error: true, +payload: Error, +loadingInfo: LoadingInfo, } | { +type: 'REQUEST_ACCESS_SUCCESS', +payload?: void, +loadingInfo: LoadingInfo, } | { +type: 'UPDATE_MULTIMEDIA_MESSAGE_MEDIA', +payload: UpdateMultimediaMessageMediaPayload, } | { +type: 'CREATE_LOCAL_MESSAGE', +payload: LocallyComposedMessageInfo, } | { +type: 'UPDATE_RELATIONSHIPS_STARTED', +payload?: void, +loadingInfo: LoadingInfo, } | { +type: 'UPDATE_RELATIONSHIPS_FAILED', +error: true, +payload: Error, +loadingInfo: LoadingInfo, } | { +type: 'UPDATE_RELATIONSHIPS_SUCCESS', +payload: RelationshipErrors, +loadingInfo: LoadingInfo, } | { +type: 'SET_THREAD_UNREAD_STATUS_STARTED', +payload: { +threadID: string, +unread: boolean, }, +loadingInfo: LoadingInfo, } | { +type: 'SET_THREAD_UNREAD_STATUS_FAILED', +error: true, +payload: Error, +loadingInfo: LoadingInfo, } | { +type: 'SET_THREAD_UNREAD_STATUS_SUCCESS', +payload: SetThreadUnreadStatusPayload, } | { +type: 'SET_USER_SETTINGS_STARTED', +payload?: void, +loadingInfo: LoadingInfo, } | { +type: 'SET_USER_SETTINGS_SUCCESS', +payload: DefaultNotificationPayload, } | { +type: 'SET_USER_SETTINGS_FAILED', +payload: Error, +loadingInfo: LoadingInfo, } | { +type: 'SEND_MESSAGE_REPORT_STARTED', +payload?: void, +loadingInfo: LoadingInfo, } | { +type: 'SEND_MESSAGE_REPORT_SUCCESS', +payload: MessageReportCreationResult, +loadingInfo: LoadingInfo, } | { +type: 'SEND_MESSAGE_REPORT_FAILED', +error: true, +payload: Error, +loadingInfo: LoadingInfo, } | { +type: 'FORCE_POLICY_ACKNOWLEDGMENT', +payload: ForcePolicyAcknowledgmentPayload, +loadingInfo: LoadingInfo, } | { +type: 'POLICY_ACKNOWLEDGMENT_STARTED', +payload?: void, +loadingInfo: LoadingInfo, } | { +type: 'POLICY_ACKNOWLEDGMENT_SUCCESS', +payload: PolicyAcknowledgmentPayload, +loadingInfo: LoadingInfo, } | { +type: 'POLICY_ACKNOWLEDGMENT_FAILED', +error: true, +payload: Error, +loadingInfo: LoadingInfo, } | { +type: 'GET_SIWE_NONCE_STARTED', +payload?: void, +loadingInfo: LoadingInfo, } | { +type: 'GET_SIWE_NONCE_SUCCESS', +payload?: void, +loadingInfo: LoadingInfo, } | { +type: 'GET_SIWE_NONCE_FAILED', +error: true, +payload: Error, +loadingInfo: LoadingInfo, } | { +type: 'SIWE_AUTH_STARTED', +payload: LogInStartingPayload, +loadingInfo: LoadingInfo, } | { +type: 'SIWE_AUTH_SUCCESS', +payload: LogInResult, +loadingInfo: LoadingInfo, } | { +type: 'SIWE_AUTH_FAILED', +error: true, +payload: Error, +loadingInfo: LoadingInfo, } | { +type: 'RECORD_NOTIF_PERMISSION_ALERT', +payload: { +time: number }, } | { +type: 'UPDATE_USER_AVATAR_STARTED', +payload: UpdateUserAvatarRequest, +loadingInfo: LoadingInfo, } | { +type: 'UPDATE_USER_AVATAR_SUCCESS', +payload: UpdateUserAvatarResponse, +loadingInfo: LoadingInfo, } | { +type: 'UPDATE_USER_AVATAR_FAILED', +error: true, +payload: Error, +loadingInfo: LoadingInfo, } | { +type: 'SEND_EDIT_MESSAGE_STARTED', +loadingInfo?: LoadingInfo, +payload?: void, } | { +type: 'SEND_EDIT_MESSAGE_SUCCESS', +payload: EditMessagePayload, +loadingInfo: LoadingInfo, } | { +type: 'SEND_EDIT_MESSAGE_FAILED', +error: true, +payload: Error, +loadingInfo: LoadingInfo, } | { +type: 'TOGGLE_MESSAGE_PIN_STARTED', +loadingInfo?: LoadingInfo, +payload?: void, } | { +type: 'TOGGLE_MESSAGE_PIN_SUCCESS', +payload: ToggleMessagePinResult, +loadingInfo: LoadingInfo, } | { +type: 'TOGGLE_MESSAGE_PIN_FAILED', +error: true, +payload: Error, +loadingInfo: LoadingInfo, } | { +type: 'FETCH_PINNED_MESSAGES_STARTED', +loadingInfo?: LoadingInfo, +payload?: void, } | { +type: 'FETCH_PINNED_MESSAGES_SUCCESS', +payload: FetchPinnedMessagesResult, +loadingInfo: LoadingInfo, } | { +type: 'FETCH_PINNED_MESSAGES_FAILED', +error: true, +payload: Error, +loadingInfo: LoadingInfo, } | { +type: 'VERIFY_INVITE_LINK_STARTED', +loadingInfo?: LoadingInfo, +payload?: void, } | { +type: 'VERIFY_INVITE_LINK_SUCCESS', +payload: InviteLinkVerificationResponse, +loadingInfo: LoadingInfo, } | { +type: 'VERIFY_INVITE_LINK_FAILED', +error: true, +payload: Error, +loadingInfo: LoadingInfo, } | { +type: 'FETCH_PRIMARY_INVITE_LINKS_STARTED', +loadingInfo?: LoadingInfo, +payload?: void, } | { +type: 'FETCH_PRIMARY_INVITE_LINKS_SUCCESS', +payload: FetchInviteLinksResponse, +loadingInfo: LoadingInfo, } | { +type: 'FETCH_PRIMARY_INVITE_LINKS_FAILED', +error: true, +payload: Error, +loadingInfo: LoadingInfo, } | { +type: 'UPDATE_CALENDAR_COMMUNITY_FILTER', +payload: string, } | { +type: 'CLEAR_CALENDAR_COMMUNITY_FILTER', +payload: void, } | { +type: 'UPDATE_CHAT_COMMUNITY_FILTER', +payload: string, } | { +type: 'CLEAR_CHAT_COMMUNITY_FILTER', +payload: void, } | { +type: 'SEARCH_MESSAGES_STARTED', +payload: void, +loadingInfo?: LoadingInfo, } | { +type: 'SEARCH_MESSAGES_SUCCESS', +payload: SearchMessagesResponse, +loadingInfo: LoadingInfo, } | { +type: 'SEARCH_MESSAGES_FAILED', +error: true, +payload: Error, +loadingInfo: LoadingInfo, } | { +type: 'CREATE_OR_UPDATE_PUBLIC_LINK_STARTED', +loadingInfo?: LoadingInfo, +payload?: void, } | { +type: 'CREATE_OR_UPDATE_PUBLIC_LINK_SUCCESS', +payload: InviteLink, +loadingInfo: LoadingInfo, } | { +type: 'CREATE_OR_UPDATE_PUBLIC_LINK_FAILED', +error: true, +payload: Error, +loadingInfo: LoadingInfo, } | { +type: 'DISABLE_INVITE_LINK_STARTED', +loadingInfo?: LoadingInfo, +payload?: void, } | { +type: 'DISABLE_INVITE_LINK_SUCCESS', +payload: DisableInviteLinkPayload, +loadingInfo: LoadingInfo, } | { +type: 'DISABLE_INVITE_LINK_FAILED', +error: true, +payload: Error, +loadingInfo: LoadingInfo, } | { +type: 'GET_OLM_SESSION_INITIALIZATION_DATA_STARTED', +loadingInfo?: LoadingInfo, +payload?: void, } | { +type: 'GET_OLM_SESSION_INITIALIZATION_DATA_SUCCESS', +payload: GetOlmSessionInitializationDataResponse, +loadingInfo: LoadingInfo, } | { +type: 'GET_OLM_SESSION_INITIALIZATION_DATA_FAILED', +error: true, +payload: Error, +loadingInfo: LoadingInfo, } | { +type: 'SET_DATA_LOADED', +payload: { +dataLoaded: boolean, }, } | { +type: 'GET_VERSION_STARTED', +loadingInfo?: LoadingInfo, +payload?: void, } | { +type: 'GET_VERSION_SUCCESS', +payload: GetVersionActionPayload, +loadingInfo: LoadingInfo, } | { +type: 'GET_VERSION_FAILED', +error: true, +payload: Error, +loadingInfo: LoadingInfo, } | { +type: 'UPDATE_LAST_COMMUNICATED_PLATFORM_DETAILS', +payload: LastCommunicatedPlatformDetails, } | { +type: 'RESET_USER_STATE', +payload?: void } | { +type: 'MODIFY_COMMUNITY_ROLE_STARTED', +loadingInfo?: LoadingInfo, +payload?: void, } | { +type: 'MODIFY_COMMUNITY_ROLE_SUCCESS', +payload: RoleModificationPayload, +loadingInfo: LoadingInfo, } | { +type: 'MODIFY_COMMUNITY_ROLE_FAILED', +error: true, +payload: Error, +loadingInfo: LoadingInfo, } | { +type: 'DELETE_COMMUNITY_ROLE_STARTED', +loadingInfo?: LoadingInfo, +payload?: void, } | { +type: 'DELETE_COMMUNITY_ROLE_SUCCESS', +payload: RoleDeletionPayload, +loadingInfo: LoadingInfo, } | { +type: 'DELETE_COMMUNITY_ROLE_FAILED', +error: true, +payload: Error, +loadingInfo: LoadingInfo, } | { +type: 'SET_ACCESS_TOKEN', +payload: string, } | { +type: 'UPDATE_THREAD_LAST_NAVIGATED', +payload: { +threadID: string, +time: number }, } | { +type: 'UPDATE_INTEGRITY_STORE', +payload: { +threadIDsToHash?: $ReadOnlyArray, +threadHashingStatus?: 'starting' | 'running' | 'completed', }, } | { +type: 'UPDATE_THEME_INFO', +payload: Shape, } | { +type: 'ADD_KEYSERVER', +payload: AddKeyserverPayload, } | { +type: 'REMOVE_KEYSERVER', +payload: RemoveKeyserverPayload, }; export type ActionPayload = ?(Object | Array<*> | $ReadOnlyArray<*> | string); export type SuperAction = { type: string, payload?: ActionPayload, loadingInfo?: LoadingInfo, error?: boolean, }; type ThunkedAction = (dispatch: Dispatch) => void; export type PromisedAction = (dispatch: Dispatch) => Promise; export type Dispatch = ((promisedAction: PromisedAction) => Promise) & ((thunkedAction: ThunkedAction) => void) & ((action: SuperAction) => boolean); // This is lifted from redux-persist/lib/constants.js // I don't want to add redux-persist to the web/server bundles... // import { REHYDRATE } from 'redux-persist'; export const rehydrateActionType = 'persist/REHYDRATE'; diff --git a/web/account/log-in-form.react.js b/web/account/log-in-form.react.js index 1ac7c9523..84579f1e7 100644 --- a/web/account/log-in-form.react.js +++ b/web/account/log-in-form.react.js @@ -1,162 +1,140 @@ // @flow import olm from '@commapp/olm'; import { useConnectModal } from '@rainbow-me/rainbowkit'; import * as React from 'react'; import { useDispatch } from 'react-redux'; import uuid from 'uuid'; import { useWalletClient } from 'wagmi'; import { isDev } from 'lib/utils/dev-utils.js'; import css from './log-in-form.css'; import SIWEButton from './siwe-button.react.js'; import SIWELoginForm from './siwe-login-form.react.js'; import TraditionalLoginForm from './traditional-login-form.react.js'; import Button from '../components/button.react.js'; import OrBreak from '../components/or-break.react.js'; import { initOlm } from '../olm/olm-utils.js'; import { updateNavInfoActionType } from '../redux/action-types.js'; -import { - setPrimaryIdentityKeys, - setNotificationIdentityKeys, - setPickledPrimaryAccount, - setPickledNotificationAccount, -} from '../redux/crypto-store-reducer.js'; +import { setCryptoStore } from '../redux/crypto-store-reducer.js'; import { useSelector } from '../redux/redux-utils.js'; function LoginForm(): React.Node { const { openConnectModal } = useConnectModal(); const { data: signer } = useWalletClient(); const dispatch = useDispatch(); - const primaryIdentityPublicKeys = useSelector( - state => state.cryptoStore.primaryIdentityKeys, - ); - const notificationIdentityPublicKeys = useSelector( - state => state.cryptoStore.notificationIdentityKeys, - ); + const cryptoStore = useSelector(state => state.cryptoStore); React.useEffect(() => { (async () => { - if ( - primaryIdentityPublicKeys !== null && - primaryIdentityPublicKeys !== undefined && - notificationIdentityPublicKeys !== null && - notificationIdentityPublicKeys !== undefined - ) { + if (cryptoStore !== null && cryptoStore !== undefined) { return; } await initOlm(); const identityAccount = new olm.Account(); identityAccount.create(); const { ed25519: identityED25519, curve25519: identityCurve25519 } = JSON.parse(identityAccount.identity_keys()); - dispatch({ - type: setPrimaryIdentityKeys, - payload: { ed25519: identityED25519, curve25519: identityCurve25519 }, - }); - const identityAccountPicklingKey = uuid.v4(); const pickledIdentityAccount = identityAccount.pickle( identityAccountPicklingKey, ); - dispatch({ - type: setPickledPrimaryAccount, - payload: { - picklingKey: identityAccountPicklingKey, - pickledAccount: pickledIdentityAccount, - }, - }); - const notificationAccount = new olm.Account(); notificationAccount.create(); const { ed25519: notificationED25519, curve25519: notificationCurve25519, } = JSON.parse(notificationAccount.identity_keys()); - dispatch({ - type: setNotificationIdentityKeys, - payload: { - ed25519: notificationED25519, - curve25519: notificationCurve25519, - }, - }); - const notificationAccountPicklingKey = uuid.v4(); const pickledNotificationAccount = notificationAccount.pickle( notificationAccountPicklingKey, ); dispatch({ - type: setPickledNotificationAccount, + type: setCryptoStore, payload: { - picklingKey: notificationAccountPicklingKey, - pickledAccount: pickledNotificationAccount, + primaryAccount: { + picklingKey: identityAccountPicklingKey, + pickledAccount: pickledIdentityAccount, + }, + primaryIdentityKeys: { + ed25519: identityED25519, + curve25519: identityCurve25519, + }, + notificationAccount: { + picklingKey: notificationAccountPicklingKey, + pickledAccount: pickledNotificationAccount, + }, + notificationIdentityKeys: { + ed25519: notificationED25519, + curve25519: notificationCurve25519, + }, }, }); })(); - }, [dispatch, notificationIdentityPublicKeys, primaryIdentityPublicKeys]); + }, [dispatch, cryptoStore]); const onQRCodeLoginButtonClick = React.useCallback(() => { dispatch({ type: updateNavInfoActionType, payload: { loginMethod: 'qr-code', }, }); }, [dispatch]); const qrCodeLoginButton = React.useMemo(() => { if (!isDev) { return null; } return (
); }, [onQRCodeLoginButtonClick]); const [siweAuthFlowSelected, setSIWEAuthFlowSelected] = React.useState(false); const onSIWEButtonClick = React.useCallback(() => { setSIWEAuthFlowSelected(true); openConnectModal && openConnectModal(); }, [openConnectModal]); const cancelSIWEAuthFlow = React.useCallback(() => { setSIWEAuthFlowSelected(false); }, []); if (siweAuthFlowSelected && signer) { return (
); } return (
{qrCodeLoginButton}
); } export default LoginForm; diff --git a/web/account/qr-code-login.react.js b/web/account/qr-code-login.react.js index 2ec3ae814..ae9da49b4 100644 --- a/web/account/qr-code-login.react.js +++ b/web/account/qr-code-login.react.js @@ -1,62 +1,62 @@ // @flow import { QRCodeSVG } from 'qrcode.react'; import * as React from 'react'; import { qrCodeLinkURL } from 'lib/facts/links.js'; import { generateKeyCommon } from 'lib/media/aes-crypto-utils-common.js'; import { uintArrayToHexString } from 'lib/media/data-utils.js'; import css from './qr-code-login.css'; import { useSelector } from '../redux/redux-utils.js'; function QrCodeLogin(): React.Node { const [qrCodeValue, setQrCodeValue] = React.useState(); const ed25519Key = useSelector( - state => state.cryptoStore.primaryIdentityKeys?.ed25519, + state => state.cryptoStore?.primaryIdentityKeys.ed25519, ); const generateQRCode = React.useCallback(async () => { try { if (!ed25519Key) { return; } const rawAESKey: Uint8Array = await generateKeyCommon(crypto); const aesKeyAsHexString: string = uintArrayToHexString(rawAESKey); const url = qrCodeLinkURL(aesKeyAsHexString, ed25519Key); setQrCodeValue(url); } catch (err) { console.error('Failed to generate QR Code:', err); } }, [ed25519Key]); React.useEffect(() => { generateQRCode(); }, [generateQRCode]); return (
Log in to Comm
Open the Comm app on your phone and scan the QR code below
How to find the scanner:
Go to Profile
Select Linked devices
Click Add on the top right
); } export default QrCodeLogin; diff --git a/web/account/siwe-login-form.react.js b/web/account/siwe-login-form.react.js index 58ae4ac16..10a884f38 100644 --- a/web/account/siwe-login-form.react.js +++ b/web/account/siwe-login-form.react.js @@ -1,269 +1,269 @@ // @flow import '@rainbow-me/rainbowkit/styles.css'; import classNames from 'classnames'; import invariant from 'invariant'; import * as React from 'react'; import { useDispatch } from 'react-redux'; import { useAccount, useWalletClient } from 'wagmi'; import { setDataLoadedActionType } from 'lib/actions/client-db-store-actions.js'; import { getSIWENonce, getSIWENonceActionTypes, siweAuth, siweAuthActionTypes, } from 'lib/actions/siwe-actions.js'; import ConnectedWalletInfo from 'lib/components/connected-wallet-info.react.js'; import SWMansionIcon from 'lib/components/SWMansionIcon.react.js'; import stores from 'lib/facts/stores.js'; import { createLoadingStatusSelector } from 'lib/selectors/loading-selectors.js'; import type { LogInStartingPayload } from 'lib/types/account-types.js'; import type { OLMIdentityKeys, SignedIdentityKeysBlob, } from 'lib/types/crypto-types.js'; import { useDispatchActionPromise, useServerCall, } from 'lib/utils/action-utils.js'; import { ServerError } from 'lib/utils/errors.js'; import { createSIWEMessage, getSIWEStatementForPublicKey, siweMessageSigningExplanationStatements, } from 'lib/utils/siwe-utils.js'; import { useSignedIdentityKeysBlob } from './account-hooks.js'; import HeaderSeparator from './header-separator.react.js'; import css from './siwe.css'; import Button from '../components/button.react.js'; import OrBreak from '../components/or-break.react.js'; import LoadingIndicator from '../loading-indicator.react.js'; import { useSelector } from '../redux/redux-utils.js'; import { webLogInExtraInfoSelector } from '../selectors/account-selectors.js'; type SIWELogInError = 'account_does_not_exist'; type SIWELoginFormProps = { +cancelSIWEAuthFlow: () => void, }; const getSIWENonceLoadingStatusSelector = createLoadingStatusSelector( getSIWENonceActionTypes, ); const siweAuthLoadingStatusSelector = createLoadingStatusSelector(siweAuthActionTypes); function SIWELoginForm(props: SIWELoginFormProps): React.Node { const { address } = useAccount(); const { data: signer } = useWalletClient(); const dispatchActionPromise = useDispatchActionPromise(); const getSIWENonceCall = useServerCall(getSIWENonce); const getSIWENonceCallLoadingStatus = useSelector( getSIWENonceLoadingStatusSelector, ); const siweAuthLoadingStatus = useSelector(siweAuthLoadingStatusSelector); const siweAuthCall = useServerCall(siweAuth); const logInExtraInfo = useSelector(webLogInExtraInfoSelector); const [siweNonce, setSIWENonce] = React.useState(null); const siweNonceShouldBeFetched = !siweNonce && getSIWENonceCallLoadingStatus !== 'loading'; React.useEffect(() => { if (!siweNonceShouldBeFetched) { return; } dispatchActionPromise( getSIWENonceActionTypes, (async () => { const response = await getSIWENonceCall(); setSIWENonce(response); })(), ); }, [dispatchActionPromise, getSIWENonceCall, siweNonceShouldBeFetched]); const primaryIdentityPublicKeys: ?OLMIdentityKeys = useSelector( - state => state.cryptoStore.primaryIdentityKeys, + state => state.cryptoStore?.primaryIdentityKeys, ); const signedIdentityKeysBlob: ?SignedIdentityKeysBlob = useSignedIdentityKeysBlob(); const callSIWEAuthEndpoint = React.useCallback( async (message: string, signature: string, extraInfo) => { invariant( signedIdentityKeysBlob, 'signedIdentityKeysBlob must be set in attemptSIWEAuth', ); try { return await siweAuthCall({ message, signature, signedIdentityKeysBlob, doNotRegister: true, ...extraInfo, }); } catch (e) { if ( e instanceof ServerError && e.message === 'account_does_not_exist' ) { setError('account_does_not_exist'); } throw e; } }, [signedIdentityKeysBlob, siweAuthCall], ); const attemptSIWEAuth = React.useCallback( (message: string, signature: string) => { const extraInfo = logInExtraInfo(); return dispatchActionPromise( siweAuthActionTypes, callSIWEAuthEndpoint(message, signature, extraInfo), undefined, ({ calendarQuery: extraInfo.calendarQuery }: LogInStartingPayload), ); }, [callSIWEAuthEndpoint, dispatchActionPromise, logInExtraInfo], ); const dispatch = useDispatch(); const onSignInButtonClick = React.useCallback(async () => { invariant(signer, 'signer must be present during SIWE attempt'); invariant(siweNonce, 'nonce must be present during SIWE attempt'); invariant( primaryIdentityPublicKeys, 'primaryIdentityPublicKeys must be present during SIWE attempt', ); const statement = getSIWEStatementForPublicKey( primaryIdentityPublicKeys.ed25519, ); const message = createSIWEMessage(address, statement, siweNonce); const signature = await signer.signMessage({ message }); await attemptSIWEAuth(message, signature); dispatch({ type: setDataLoadedActionType, payload: { dataLoaded: true, }, }); }, [ address, attemptSIWEAuth, primaryIdentityPublicKeys, signer, siweNonce, dispatch, ]); const { cancelSIWEAuthFlow } = props; const backButtonColor = React.useMemo( () => ({ backgroundColor: '#211E2D' }), [], ); const signInButtonColor = React.useMemo( () => ({ backgroundColor: '#6A20E3' }), [], ); const [error, setError] = React.useState(); const mainMiddleAreaClassName = classNames({ [css.mainMiddleArea]: true, [css.hidden]: !!error, }); const errorOverlayClassNames = classNames({ [css.errorOverlay]: true, [css.hidden]: !error, }); if ( siweAuthLoadingStatus === 'loading' || !siweNonce || !primaryIdentityPublicKeys || !signedIdentityKeysBlob ) { return (
); } let errorText; if (error === 'account_does_not_exist') { errorText = ( <>

No Comm account found for that Ethereum wallet!

We require that users register on their mobile devices. Comm relies on a primary device capable of scanning QR codes in order to authorize secondary devices.

You can install our iOS app  here , or our Android app  here .

); } return (

Sign in with Ethereum

Wallet Connected

{siweMessageSigningExplanationStatements}

By signing up, you agree to our{' '} Terms of Use &{' '} Privacy Policy.

{errorText}
); } export default SIWELoginForm; diff --git a/web/redux/crypto-store-reducer.js b/web/redux/crypto-store-reducer.js index 8bd53dd40..cafc9f905 100644 --- a/web/redux/crypto-store-reducer.js +++ b/web/redux/crypto-store-reducer.js @@ -1,60 +1,28 @@ // @flow import { logOutActionTypes, deleteAccountActionTypes, } from 'lib/actions/user-actions.js'; import type { CryptoStore } from 'lib/types/crypto-types.js'; import { setNewSessionActionType } from 'lib/utils/action-utils.js'; import type { Action } from './redux-setup.js'; -const setPrimaryIdentityKeys = 'SET_PRIMARY_IDENTITY_KEYS'; -const setNotificationIdentityKeys = 'SET_NOTIFICATION_IDENTITY_KEYS'; -const setPickledPrimaryAccount = 'SET_PICKLED_PRIMARY_ACCOUNT'; -const setPickledNotificationAccount = 'SET_PICKLED_NOTIFICATION_ACCOUNT'; +const setCryptoStore = 'SET_CRYPTO_STORE'; -function reduceCryptoStore(state: CryptoStore, action: Action): CryptoStore { - if (action.type === setPrimaryIdentityKeys) { - return { - ...state, - primaryIdentityKeys: action.payload, - }; - } else if (action.type === setNotificationIdentityKeys) { - return { - ...state, - notificationIdentityKeys: action.payload, - }; - } else if (action.type === setPickledPrimaryAccount) { - return { - ...state, - primaryAccount: action.payload, - }; - } else if (action.type === setPickledNotificationAccount) { - return { - ...state, - notificationAccount: action.payload, - }; +function reduceCryptoStore(state: ?CryptoStore, action: Action): ?CryptoStore { + if (action.type === setCryptoStore) { + return action.payload; } else if ( action.type === logOutActionTypes.success || action.type === deleteAccountActionTypes.success || (action.type === setNewSessionActionType && action.payload.sessionChange.cookieInvalidated) ) { - return { - primaryAccount: null, - primaryIdentityKeys: null, - notificationAccount: null, - notificationIdentityKeys: null, - }; + return null; } return state; } -export { - setPrimaryIdentityKeys, - setNotificationIdentityKeys, - setPickledPrimaryAccount, - setPickledNotificationAccount, - reduceCryptoStore, -}; +export { setCryptoStore, reduceCryptoStore }; diff --git a/web/redux/default-state.js b/web/redux/default-state.js index 4a7cd6944..3ce8a25f0 100644 --- a/web/redux/default-state.js +++ b/web/redux/default-state.js @@ -1,95 +1,90 @@ // @flow import { defaultEnabledApps } from 'lib/types/enabled-apps.js'; import { defaultCalendarFilters } from 'lib/types/filter-types.js'; import { defaultConnectionInfo } from 'lib/types/socket-types.js'; import { defaultGlobalThemeInfo } from 'lib/types/theme-types.js'; import { defaultNotifPermissionAlertInfo } from 'lib/utils/push-alerts.js'; import { ashoatKeyserverID } from 'lib/utils/validation-utils.js'; import type { AppState } from './redux-setup.js'; declare var keyserverURL: string; const defaultWebState: AppState = Object.freeze({ navInfo: { activeChatThreadID: null, startDate: '', endDate: '', tab: 'chat', }, currentUserInfo: null, draftStore: { drafts: {} }, entryStore: { entryInfos: {}, daysToEntries: {}, lastUserInteractionCalendar: 0, }, threadStore: { threadInfos: {}, }, userStore: { userInfos: {}, }, messageStore: { messages: {}, threads: {}, local: {}, currentAsOf: { [ashoatKeyserverID]: 0 }, }, windowActive: true, pushApiPublicKey: null, - cryptoStore: { - primaryAccount: null, - primaryIdentityKeys: null, - notificationAccount: null, - notificationIdentityKeys: null, - }, + cryptoStore: null, windowDimensions: { width: window.width, height: window.height }, loadingStatuses: {}, calendarFilters: defaultCalendarFilters, dataLoaded: false, notifPermissionAlertInfo: defaultNotifPermissionAlertInfo, watchedThreadIDs: [], lifecycleState: 'active', enabledApps: defaultEnabledApps, reportStore: { enabledReports: { crashReports: false, inconsistencyReports: false, mediaReports: false, }, queuedReports: [], }, nextLocalID: 0, _persist: null, userPolicies: {}, commServicesAccessToken: null, inviteLinksStore: { links: {}, }, actualizedCalendarQuery: { startDate: '', endDate: '', filters: defaultCalendarFilters, }, communityPickerStore: { chat: null, calendar: null }, keyserverStore: { keyserverInfos: { [ashoatKeyserverID]: { cookie: null, updatesCurrentAsOf: 0, urlPrefix: keyserverURL, connection: { ...defaultConnectionInfo }, lastCommunicatedPlatformDetails: null, deviceToken: null, }, }, }, threadActivityStore: {}, initialStateLoaded: false, integrityStore: { threadHashes: {}, threadHashingStatus: 'starting' }, globalThemeInfo: defaultGlobalThemeInfo, }); export { defaultWebState }; diff --git a/web/redux/redux-setup.js b/web/redux/redux-setup.js index 97174c04c..efd470f12 100644 --- a/web/redux/redux-setup.js +++ b/web/redux/redux-setup.js @@ -1,341 +1,325 @@ // @flow import invariant from 'invariant'; import type { PersistState } from 'redux-persist/es/types.js'; import { logOutActionTypes, deleteAccountActionTypes, } from 'lib/actions/user-actions.js'; import { reportStoreOpsHandlers } from 'lib/ops/report-store-ops.js'; import baseReducer from 'lib/reducers/master-reducer.js'; import { mostRecentlyReadThreadSelector } from 'lib/selectors/thread-selectors.js'; import { isLoggedIn } from 'lib/selectors/user-selectors.js'; import { invalidSessionDowngrade } from 'lib/shared/session-utils.js'; import type { Shape } from 'lib/types/core.js'; -import type { - CryptoStore, - OLMIdentityKeys, - PickledOLMAccount, -} from 'lib/types/crypto-types.js'; +import type { CryptoStore } from 'lib/types/crypto-types.js'; import type { DraftStore } from 'lib/types/draft-types.js'; import type { EnabledApps } from 'lib/types/enabled-apps.js'; import type { EntryStore, CalendarQuery } from 'lib/types/entry-types.js'; import { type CalendarFilter } from 'lib/types/filter-types.js'; import type { IntegrityStore } from 'lib/types/integrity-types.js'; import type { KeyserverStore } from 'lib/types/keyserver-types.js'; import type { LifecycleState } from 'lib/types/lifecycle-state-types.js'; import type { InviteLinksStore } from 'lib/types/link-types.js'; import type { LoadingStatus } from 'lib/types/loading-types.js'; import type { MessageStore } from 'lib/types/message-types.js'; import type { UserPolicies } from 'lib/types/policy-types.js'; import type { BaseAction } from 'lib/types/redux-types.js'; import type { ReportStore } from 'lib/types/report-types.js'; import type { GlobalThemeInfo } from 'lib/types/theme-types.js'; import type { ThreadActivityStore } from 'lib/types/thread-activity-types'; import type { ThreadStore } from 'lib/types/thread-types.js'; import type { CurrentUserInfo, UserStore } from 'lib/types/user-types.js'; import { setNewSessionActionType } from 'lib/utils/action-utils.js'; import type { NotifPermissionAlertInfo } from 'lib/utils/push-alerts.js'; import { ashoatKeyserverID } from 'lib/utils/validation-utils.js'; import { updateWindowActiveActionType, updateNavInfoActionType, updateWindowDimensionsActionType, setInitialReduxState, } from './action-types.js'; import { reduceCommunityPickerStore } from './community-picker-reducer.js'; -import { - reduceCryptoStore, - setPrimaryIdentityKeys, - setNotificationIdentityKeys, - setPickledNotificationAccount, - setPickledPrimaryAccount, -} from './crypto-store-reducer.js'; +import { reduceCryptoStore, setCryptoStore } from './crypto-store-reducer.js'; import reduceNavInfo from './nav-reducer.js'; import { onStateDifference } from './redux-debug-utils.js'; import { getVisibility } from './visibility.js'; import { getDatabaseModule } from '../database/database-module-provider.js'; import { activeThreadSelector } from '../selectors/nav-selectors.js'; import { type NavInfo } from '../types/nav-types.js'; import type { InitialReduxState } from '../types/redux-types.js'; import { workerRequestMessageTypes } from '../types/worker-types.js'; export type WindowDimensions = { width: number, height: number }; export type CommunityPickerStore = { +chat: ?string, +calendar: ?string, }; export type AppState = { +navInfo: NavInfo, +currentUserInfo: ?CurrentUserInfo, +draftStore: DraftStore, +entryStore: EntryStore, +threadStore: ThreadStore, +userStore: UserStore, +messageStore: MessageStore, +loadingStatuses: { [key: string]: { [idx: number]: LoadingStatus } }, +calendarFilters: $ReadOnlyArray, +communityPickerStore: CommunityPickerStore, +windowDimensions: WindowDimensions, +notifPermissionAlertInfo: NotifPermissionAlertInfo, +actualizedCalendarQuery: CalendarQuery, +watchedThreadIDs: $ReadOnlyArray, +lifecycleState: LifecycleState, +enabledApps: EnabledApps, +reportStore: ReportStore, +nextLocalID: number, +dataLoaded: boolean, +windowActive: boolean, +userPolicies: UserPolicies, - +cryptoStore: CryptoStore, + +cryptoStore: ?CryptoStore, +pushApiPublicKey: ?string, +_persist: ?PersistState, +commServicesAccessToken: ?string, +inviteLinksStore: InviteLinksStore, +keyserverStore: KeyserverStore, +threadActivityStore: ThreadActivityStore, +initialStateLoaded: boolean, +integrityStore: IntegrityStore, +globalThemeInfo: GlobalThemeInfo, }; export type Action = | BaseAction | { type: 'UPDATE_NAV_INFO', payload: Shape } | { type: 'UPDATE_WINDOW_DIMENSIONS', payload: WindowDimensions, } | { type: 'UPDATE_WINDOW_ACTIVE', payload: boolean, } - | { +type: 'SET_PRIMARY_IDENTITY_KEYS', payload: ?OLMIdentityKeys } - | { +type: 'SET_NOTIFICATION_IDENTITY_KEYS', payload: ?OLMIdentityKeys } - | { +type: 'SET_PICKLED_PRIMARY_ACCOUNT', payload: ?PickledOLMAccount } - | { +type: 'SET_PICKLED_NOTIFICATION_ACCOUNT', payload: ?PickledOLMAccount } + | { +type: 'SET_CRYPTO_STORE', payload: CryptoStore } | { +type: 'SET_INITIAL_REDUX_STATE', payload: InitialReduxState }; export function reducer(oldState: AppState | void, action: Action): AppState { invariant(oldState, 'should be set'); let state = oldState; if (action.type === setInitialReduxState) { const { userInfos, keyserverInfos, ...rest } = action.payload; const newKeyserverInfos = { ...state.keyserverStore.keyserverInfos }; for (const keyserverID in keyserverInfos) { newKeyserverInfos[keyserverID] = { ...newKeyserverInfos[keyserverID], ...keyserverInfos[keyserverID], }; } return validateState(oldState, { ...state, ...rest, userStore: { userInfos }, keyserverStore: { ...state.keyserverStore, keyserverInfos: newKeyserverInfos, }, initialStateLoaded: true, }); } else if (action.type === updateWindowDimensionsActionType) { return validateState(oldState, { ...state, windowDimensions: action.payload, }); } else if (action.type === updateWindowActiveActionType) { return validateState(oldState, { ...state, windowActive: action.payload, }); } else if (action.type === setNewSessionActionType) { if ( invalidSessionDowngrade( oldState, action.payload.sessionChange.currentUserInfo, action.payload.preRequestUserState, ) ) { return oldState; } state = { ...state, keyserverStore: { ...state.keyserverStore, keyserverInfos: { ...state.keyserverStore.keyserverInfos, [ashoatKeyserverID]: { ...state.keyserverStore.keyserverInfos[ashoatKeyserverID], sessionID: action.payload.sessionChange.sessionID, }, }, }, }; } else if ( (action.type === logOutActionTypes.success && invalidSessionDowngrade( oldState, action.payload.currentUserInfo, action.payload.preRequestUserState, )) || (action.type === deleteAccountActionTypes.success && invalidSessionDowngrade( oldState, action.payload.currentUserInfo, action.payload.preRequestUserState, )) ) { return oldState; } if ( action.type !== updateNavInfoActionType && - action.type !== setPrimaryIdentityKeys && - action.type !== setNotificationIdentityKeys && - action.type !== setPickledPrimaryAccount && - action.type !== setPickledNotificationAccount + action.type !== setCryptoStore ) { const baseReducerResult = baseReducer(state, action, onStateDifference); state = baseReducerResult.state; const { storeOperations: { draftStoreOperations, reportStoreOperations }, } = baseReducerResult; if (draftStoreOperations.length > 0 || reportStoreOperations.length > 0) { (async () => { const databaseModule = await getDatabaseModule(); const isSupported = await databaseModule.isDatabaseSupported(); if (!isSupported) { return; } const convertedReportStoreOperations = reportStoreOpsHandlers.convertOpsToClientDBOps(reportStoreOperations); await databaseModule.schedule({ type: workerRequestMessageTypes.PROCESS_STORE_OPERATIONS, storeOperations: { draftStoreOperations, reportStoreOperations: convertedReportStoreOperations, }, }); })(); } } const communityPickerStore = reduceCommunityPickerStore( state.communityPickerStore, action, ); state = { ...state, navInfo: reduceNavInfo( state.navInfo, action, state.threadStore.threadInfos, ), cryptoStore: reduceCryptoStore(state.cryptoStore, action), communityPickerStore, }; return validateState(oldState, state); } function validateState(oldState: AppState, state: AppState): AppState { if ( (state.navInfo.activeChatThreadID && !state.navInfo.pendingThread && !state.threadStore.threadInfos[state.navInfo.activeChatThreadID]) || (!state.navInfo.activeChatThreadID && isLoggedIn(state)) ) { // Makes sure the active thread always exists state = { ...state, navInfo: { ...state.navInfo, activeChatThreadID: mostRecentlyReadThreadSelector(state), }, }; } const activeThread = activeThreadSelector(state); if ( activeThread && !state.navInfo.pendingThread && state.threadStore.threadInfos[activeThread].currentUser.unread && getVisibility().hidden() ) { console.warn( `thread ${activeThread} is active and unread, ` + 'but visibilityjs reports the window is not visible', ); } if ( activeThread && !state.navInfo.pendingThread && state.threadStore.threadInfos[activeThread].currentUser.unread && typeof document !== 'undefined' && document && 'hasFocus' in document && !document.hasFocus() ) { console.warn( `thread ${activeThread} is active and unread, ` + 'but document.hasFocus() is false', ); } if ( activeThread && !getVisibility().hidden() && typeof document !== 'undefined' && document && 'hasFocus' in document && document.hasFocus() && !state.navInfo.pendingThread && state.threadStore.threadInfos[activeThread].currentUser.unread ) { // Makes sure a currently focused thread is never unread state = { ...state, threadStore: { ...state.threadStore, threadInfos: { ...state.threadStore.threadInfos, [activeThread]: { ...state.threadStore.threadInfos[activeThread], currentUser: { ...state.threadStore.threadInfos[activeThread].currentUser, unread: false, }, }, }, }, }; } const oldActiveThread = activeThreadSelector(oldState); if ( activeThread && oldActiveThread !== activeThread && state.messageStore.threads[activeThread] ) { const now = Date.now(); state = { ...state, threadActivityStore: { ...state.threadActivityStore, [activeThread]: { ...state.threadActivityStore[activeThread], lastNavigatedTo: now, }, }, }; } return state; } diff --git a/web/selectors/socket-selectors.js b/web/selectors/socket-selectors.js index 672650aa4..1f56f55bd 100644 --- a/web/selectors/socket-selectors.js +++ b/web/selectors/socket-selectors.js @@ -1,162 +1,157 @@ // @flow import olm from '@commapp/olm'; import _memoize from 'lodash/memoize.js'; import { createSelector } from 'reselect'; import { sessionIDSelector, urlPrefixSelector, cookieSelector, } from 'lib/selectors/keyserver-selectors.js'; import { getClientResponsesSelector, sessionStateFuncSelector, } from 'lib/selectors/socket-selectors.js'; import { createOpenSocketFunction } from 'lib/shared/socket-utils.js'; import type { - OLMIdentityKeys, - PickledOLMAccount, SignedIdentityKeysBlob, IdentityKeysBlob, + CryptoStore, } from 'lib/types/crypto-types.js'; import type { ClientServerRequest, ClientClientResponse, } from 'lib/types/request-types.js'; import type { SessionIdentification, SessionState, } from 'lib/types/session-types.js'; import type { OneTimeKeyGenerator } from 'lib/types/socket-types.js'; import { initOlm } from '../olm/olm-utils.js'; import type { AppState } from '../redux/redux-setup.js'; const baseOpenSocketSelector: ( keyserverID: string, ) => (state: AppState) => ?() => WebSocket = keyserverID => createSelector(urlPrefixSelector(keyserverID), (urlPrefix: ?string) => { if (!urlPrefix) { return null; } return createOpenSocketFunction(urlPrefix); }); const openSocketSelector: ( keyserverID: string, ) => (state: AppState) => ?() => WebSocket = _memoize(baseOpenSocketSelector); const baseSessionIdentificationSelector: ( keyserverID: string, ) => (state: AppState) => SessionIdentification = keyserverID => createSelector( cookieSelector(keyserverID), sessionIDSelector(keyserverID), (cookie: ?string, sessionID: ?string): SessionIdentification => ({ cookie, sessionID, }), ); const sessionIdentificationSelector: ( keyserverID: string, ) => (state: AppState) => SessionIdentification = _memoize( baseSessionIdentificationSelector, ); const getSignedIdentityKeysBlobSelector: ( state: AppState, ) => ?() => Promise = createSelector( - (state: AppState) => state.cryptoStore.primaryAccount, - (state: AppState) => state.cryptoStore.primaryIdentityKeys, - (state: AppState) => state.cryptoStore.notificationIdentityKeys, - ( - primaryAccount: ?PickledOLMAccount, - primaryIdentityKeys: ?OLMIdentityKeys, - notificationIdentityKeys: ?OLMIdentityKeys, - ) => { - if (!primaryAccount || !primaryIdentityKeys || !notificationIdentityKeys) { + (state: AppState) => state.cryptoStore, + (cryptoStore: ?CryptoStore) => { + if (!cryptoStore) { return null; } return async () => { await initOlm(); + const { primaryAccount, primaryIdentityKeys, notificationIdentityKeys } = + cryptoStore; const primaryOLMAccount = new olm.Account(); primaryOLMAccount.unpickle( primaryAccount.picklingKey, primaryAccount.pickledAccount, ); const identityKeysBlob: IdentityKeysBlob = { primaryIdentityPublicKeys: primaryIdentityKeys, notificationIdentityPublicKeys: notificationIdentityKeys, }; const payloadToBeSigned: string = JSON.stringify(identityKeysBlob); const signedIdentityKeysBlob: SignedIdentityKeysBlob = { payload: payloadToBeSigned, signature: primaryOLMAccount.sign(payloadToBeSigned), }; return signedIdentityKeysBlob; }; }, ); const webGetClientResponsesSelector: ( state: AppState, ) => ( serverRequests: $ReadOnlyArray, ) => Promise<$ReadOnlyArray> = createSelector( getClientResponsesSelector, getSignedIdentityKeysBlobSelector, (state: AppState) => state.navInfo.tab === 'calendar', ( getClientResponsesFunc: ( calendarActive: boolean, oneTimeKeyGenerator: ?OneTimeKeyGenerator, getSignedIdentityKeysBlob: ?() => Promise, getInitialNotificationsEncryptedMessage: ?() => Promise, serverRequests: $ReadOnlyArray, ) => Promise<$ReadOnlyArray>, getSignedIdentityKeysBlob: ?() => Promise, calendarActive: boolean, ) => (serverRequests: $ReadOnlyArray) => getClientResponsesFunc( calendarActive, null, getSignedIdentityKeysBlob, null, serverRequests, ), ); const baseWebSessionStateFuncSelector: ( keyserverID: string, ) => (state: AppState) => () => SessionState = keyserverID => createSelector( sessionStateFuncSelector(keyserverID), (state: AppState) => state.navInfo.tab === 'calendar', ( sessionStateFunc: (calendarActive: boolean) => SessionState, calendarActive: boolean, ) => () => sessionStateFunc(calendarActive), ); const webSessionStateFuncSelector: ( keyserverID: string, ) => (state: AppState) => () => SessionState = _memoize( baseWebSessionStateFuncSelector, ); export { openSocketSelector, sessionIdentificationSelector, getSignedIdentityKeysBlobSelector, webGetClientResponsesSelector, webSessionStateFuncSelector, }; diff --git a/web/selectors/tunnelbroker-selectors.js b/web/selectors/tunnelbroker-selectors.js index a83868b9d..e39d3b0c1 100644 --- a/web/selectors/tunnelbroker-selectors.js +++ b/web/selectors/tunnelbroker-selectors.js @@ -1,30 +1,30 @@ // @flow import { createSelector } from 'reselect'; import type { ConnectionInitializationMessage } from 'lib/types/tunnelbroker/session-types.js'; import type { AppState } from '../redux/redux-setup.js'; export const createTunnelbrokerInitMessage: AppState => ?ConnectionInitializationMessage = createSelector( - state => state.cryptoStore.primaryIdentityKeys?.ed25519, + state => state.cryptoStore?.primaryIdentityKeys.ed25519, state => state.commServicesAccessToken, state => state.currentUserInfo?.id, ( deviceID: ?string, accessToken: ?string, userID: ?string, ): ?ConnectionInitializationMessage => { if (!deviceID || !accessToken || !userID) { return null; } return ({ type: 'ConnectionInitializationMessage', deviceID, accessToken, userID, deviceType: 'web', }: ConnectionInitializationMessage); }, );